<?php

abstract class Jf_Form_Validate_Abstract extends Jf_Validate_Abstract {

    const FIELDS = 'fields';

    const FILTER = 'filter';

    const VALIDATE = 'validate';

    const VALIDATOR = 'validator';

    const RULE_WILDCARD = '*';

    const FILTER_CHAIN = 'filterChain';

    const RULE = 'rule';

    protected $_filters = array();

    protected $_validateRules = array();

    protected $_filterRules = array();

    protected $_options = array();

    protected $_processed = false;

    protected $_validFields = array();

    protected $_fields = array();

    protected $_defaults = array('isRequried' => "Please you give a value for '%field%'");

    protected $_messageTemplates = array();

    protected $_loaders = array();

    protected $_translatorDisabled = true;

    protected $_ruleBreak = array();

    protected $_onErrorBreak = false;

    public function isValid ($data) {
        $this -> _data = $data;
        $this -> _process ();
        return  ! $this -> _error;
    }

    protected function _validate ($validateRules = null, $fields = null) {
        if ( ! $this -> _validateRules) {
            $this -> _validFields = $this -> _data;
            $this -> _data = array();
            return;
        } elseif (null === $validateRules) {
            $validateRules = $this -> _validateRules;
        }
        if (null === $fields) {
            $fields = $this -> _data;
        }
        foreach ($validateRules as $rule => $validates) {
            if ( ! empty ($validates['childrens'])) {
                $this -> _validate ($validates['childrens'], $fields[$rule]);
                continue;
            } else {
                $this -> _ruleBreak[$rule] = null;
            }
            // $fields[$rule] is data value
            if (empty ($fields[$rule])) {
                if (empty ($validates['isRequried']) || false === $validates['isRequried']) {
                    continue;
                } elseif (true == $validates['isRequried']) {
                    $this -> setError ($this -> getMessage ('isRequried', $rule), $rule);
                    if ($this -> _onErrorBreak) {
                        break;
                    } else {
                        continue;
                    }
                }
            }
            unset ($validates['isRequried']);
            foreach ($validates as $validate => $params) {
                if (true === $this -> _ruleBreak[$rule]) {
                    continue;
                } else {
                    if (is_string ($params)) {
                        $validate = $params;
                        $params = null;
                    }
                    if (false === $this -> _validateRule ($rule, $fields[$rule], $validate, $params)) {
                        if ($this -> _onErrorBreak) {
                            break;
                        } else {
                            $this -> _ruleBreak[$rule] = true;
                        }
                    }
                }
            }
        }
    }

    protected function _validateRule ($rule, $field, $validate, $params = null) {
        $validator = $this -> getValidator ($validate, $params);
        if (isset ($this -> _messageTemplates[$validate])) {
            $validator -> setMessages ($this -> _messageTemplates[$validate]);
        }
        if ( ! $validator -> isValid ($field)) {
            $this -> setError ($validator -> getMessages (), $rule);
            return false;
        }
        return true;
    }

    protected function _filter () {
        foreach ($this -> _filterRules as $ruleName => &$filterRule) {
            if ( ! is_array ($filterRule)) {
                $filterRule = array($filterRule);
            }
            $filterList = array();
            foreach ($filterRule as $key => $value) {
                if (is_int ($key)) {
                    $filterList[] = $value;
                }
            }
            $filterRule[self::RULE] = $ruleName;
            if ( ! isset ($filterRule[self::FIELDS])) {
                $filterRule[self::FIELDS] = $ruleName;
            }
            if ( ! isset ($filterRule[self::FILTER_CHAIN])) {
                $filterRule[self::FILTER_CHAIN] = new Zend_Filter ();
                foreach ($filterList as $filter) {
                    if (is_string ($filter) || is_array ($filter)) {
                        $filter = $this -> _getFilter ($filter);
                    }
                    $filterRule[self::FILTER_CHAIN] -> addFilter ($filter);
                }
            }
            // var_dump($filterRule);
            /**
             * If the ruleName is the special wildcard rule,
             * then apply the filter chain to all input data.
             * Else just process the field named by the rule.
             */
            if ($ruleName == self::RULE_WILDCARD) {
                foreach (array_keys ($this -> _data) as $field) {
                    $this -> _filterRule (array_merge ($filterRule, array(self::FIELDS => $field)));
                }
            } else {
                $this -> _filterRule ($filterRule);
            }
        }
    }

    protected function _filterRule (array $filterRule) {
        $field = $filterRule[self::FIELDS];
        if ( ! array_key_exists ($field, $this -> _data)) {
            return;
        }
        if (is_array ($this -> _data[$field])) {
            foreach ($this -> _data[$field] as $key => $value) {
                $this -> _data[$field][$key] = $filterRule[self::FILTER_CHAIN] -> filter ($value);
            }
        } else {
            $this -> _data[$field] = $filterRule[self::FILTER_CHAIN] -> filter ($this -> _data[$field]);
        }
    }

    protected function _process () {
        if ($this -> _processed === false) {
            $this -> _validate ();
            $this -> _filter ();
            $this -> _processed = true;
        }
    }

    protected function getMessage ($rule, $field) {
        if (isset ($this -> _messageTemplates['isRequried'])) {
            $message = $this -> _messageTemplates['isRequried'];
        } else {
            $message = $this -> _defaults[$rule];
        }
        if (null !== ($translator = $this -> getTranslator ())) {
            if ($translator -> isTranslated ($field)) {
                $message = $translator -> translate ($field);
            } else {
                $message = $translator -> translate ($message);
            }
        }
        return str_replace ('%field%', $field, $message);
    }

    public function setTranslator ($translator = null) {
        if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
            $this -> _translator = $translator;
        } elseif ($translator instanceof Zend_Translate) {
            $this -> _translator = $translator -> getAdapter ();
        } else {
            throw new Jf_Exception ('Invalid translator specified');
        }
        return $this;
    }

    public function translatorIsDisabled () {
        return $this -> _translatorDisabled;
    }

    public function getTranslator () {
        if ($this -> translatorIsDisabled ()) {
            return null;
        }
        if ($this -> _translator === null) {
            if (Jf_Registry::isRegistered ('Zend_Translate')) {
                $translator = Jf_Registry::get ('Zend_Translate');
                if ($translator instanceof Zend_Translate_Adapter) {
                    return $translator;
                } elseif ($translator instanceof Zend_Translate) {
                    return $translator -> getAdapter ();
                }
            }
        }
        return $this -> _translator;
    }

    public function addNamespace ($namespaces) {
        if ( ! is_array ($namespaces)) {
            $namespaces = array($namespaces);
        }
        foreach ($namespaces as $namespace) {
            $prefix = $namespace;
            $path = str_replace ('_', DIRECTORY_SEPARATOR, $prefix);
            $this -> addFilterPrefixPath ($prefix, $path);
            $this -> addValidatorPrefixPath ($prefix, $path);
        }
        return $this;
    }

    public function addFilterPrefixPath ($prefix, $path) {
        $this -> getPluginLoader (self::FILTER) -> addPrefixPath ($prefix, $path);
        return $this;
    }

    public function addValidatorPrefixPath ($prefix, $path) {
        $this -> getPluginLoader (self::VALIDATE) -> addPrefixPath ($prefix, $path);
        return $this;
    }

    protected function getValidator ($classBaseName, $args = array()) {
        return $this -> _getFilterOrValidator (self::VALIDATE, $classBaseName, $args);
    }

    protected function _getFilter ($classBaseName, $args = array()) {
        return $this -> _getFilterOrValidator (self::FILTER, $classBaseName, $args);
    }

    protected function _getFilterOrValidator ($type, $classBaseName, $args = array()) {
        if (is_array ($classBaseName)) {
            $args = $classBaseName;
            $classBaseName = array_shift ($args);
        }
        $interfaceName = 'Zend_' . ucfirst ($type) . '_Interface';
        $className = $this -> getPluginLoader ($type) -> load (ucfirst ($classBaseName));
        $class = new ReflectionClass ($className);
        if ( ! $class -> implementsInterface ($interfaceName)) {
            throw new Jf_Exception ("Class '$className' based on basename '$classBaseName' must implement the '$interfaceName' interface");
        }
        if ($class -> hasMethod ('__construct')) {
            $object = $class -> newInstanceArgs ($args);
        } else {
            $object = $class -> newInstance ();
        }
        return $object;
    }

    protected function getPluginLoader ($type) {
        if ( ! isset ($this -> _loaders[$type])) {
            switch ($type) {
                case self::FILTER :
                    $prefixSegment = 'Zend_Filter_';
                    $pathSegment = 'Zend/Filter/';
                    break;
                case self::VALIDATE :
                    $prefixSegment = 'Zend_Validate_';
                    $pathSegment = 'Zend/Validate/';
                    break;
                default :
                    throw new Jf_Exception (sprintf ('Invalid type "%s" provided to getPluginLoader()', $type));
            }
            $this -> _loaders[$type] = new Zend_Loader_PluginLoader (array($prefixSegment => $pathSegment));
        }
        return $this -> _loaders[$type];
    }

}

